# -*- coding: utf-8 -*-

# 授权码授权模式( Authorization Code Grant )
# 隐式授权模式( Implicit Grant )
# 密码授权模式( Resource Owner Password Credentials Grant )
# 客户端凭证授权模式( Client Credentials Grant )
from jose import jwt, JWTError
from fastapi import APIRouter, Depends, Header, HTTPException
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from typing import List, Optional
from pydantic import BaseModel
from starlette import status
from passlib.context import CryptContext
import json

from datetime import date, datetime, timedelta

chapter04 = APIRouter()

"""
OAuth2 密码模式和FastAPI的 OAuth2PasswordBearer
"""


"""
OAuth2PasswordBearer是接收URL作为参数的一个类，客户端会向该URL发送username和password参数
OAuth2PasswordBearer并不会创建相应的URL路径操作，只是指明客户端用来请求Token的URL地址

"""
# oauth2_schema = OAuth2PasswordBearer(tokenUrl="chapter04/token")
oauth2_schema = OAuth2PasswordBearer(tokenUrl="chapter04/jwt/token")

@chapter04.get('/oauth2_password_bearer')
async def oauth2_password_bearer(token: str = Depends(oauth2_schema)):
    return {"token": token}

"""基于 Password 和 Token Bearer 的OAuth2 认证"""
fake_users_db = {
    "Tom": {
        "username": "Tom",
        "full_name": "Tom",
        "email": "Tome@example.com",
        "hashed_password": "fakehashedsecret",
        "disabled": True
    },
    "Jerry": {
        "username": "Jerry",
        "full_name": "Jerry",
        "email": "Jerry@example.com",
        "hashed_password": "fakehashedsecret",
        "disabled": True
    }
}

def fake_hash_password(password: str):
    return "fakehashed" + password

class User(BaseModel):
    username: str
    email: Optional[str] = None
    full_name: Optional[str] = None
    disabled: Optional[bool] = None

class UserInDB(User):
    hashed_password: str

def get_user(db, username: str):
    if username in fake_users_db:
        user_dict = db[username]
        return UserInDB(user_dict)
    pass

def fake_decode_token(token: str):
    user = get_user(fake_users_db, token)
    return user

def get_current_user(token: str = Depends(oauth2_schema)):

    user = fake_decode_token(token)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid authentication credentials",
            headers={"WWW-Authenticate": "Bearer"} # OAuth2请求规范，如果认证失败，请求头中返回WWW-Authenticate
        )
    return user

def get_current_active_user(current_user: User = Depends(get_current_user)):
    if current_user.disabled:
        raise HTTPException(
            status.HTTP_400_BAD_REQUEST,
            detail="Inactive user"
        )
    return current_user

@chapter04.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    user_dict = fake_users_db.get(form_data.username)
    if not user_dict:
        raise HTTPException(
            status=status.HTTP_400_BAD_REQUEST,
            detail="Incorrect username or password"
        )
    user = UserInDB(**user_dict)
    hashed_password = fake_hash_password(form_data.password)
    if not hashed_password == user.hashed_password:
        raise HTTPException(
            status=status.HTTP_400_BAD_REQUEST,
            detail="Incorrect username or password"
        )

    return {"access_token": user.username, "token_type": "bearer"}

@chapter04.get('/users/me')
async def read_users_me(current_user: User = Depends(get_current_active_user)):
    return current_user

"""OAuth2 with Password (and hashing), Bearer with JWT tokens 基于web token"""
fake_users_db.update({
    "Tom": {
        "username": "Tom",
        "full_name": "Tom",
        "email": "Tome@example.com",
        # "hashed_password": "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b",
        "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
        "disabled": True
    }
})

# 通过 openssl rand -hex 32 生成的随机密钥
# SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
SECRET_KEY = "dc393487a84ddf9da61fe0180ef295cf0642ecbc5d678a1589ef2e26b35fce9c"
# 加密算法
ALGORITHM = "HS256"
# 过期时间，分钟
ACCESS_TOKEN_EXPIRE_MINUTES = 30

class Token(BaseModel):
    """
    返回给用户的token
    """
    access_token: str
    token_type: str

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def verity_password(plain_password: str, hashed_password: str):
    return pwd_context.verify(plain_password, hashed_password)


def jwt_get_user(db, username: str):
    if username in fake_users_db:
        user_dict = fake_users_db[username]
        return UserInDB(**user_dict)

def jwt_authenticate_user(db, username: str, password: str):
    user = jwt_get_user(db=db, username=username)
    if not user:
        return False
    if not verity_password(plain_password=password, hashed_password=user.hashed_password):
        return False
    return user

def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)

    to_encode.update({"exp": expire})

    # 加密
    encoded_jwt = jwt.encode(claims=to_encode, key=SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt


@chapter04.post("/jwt/token", response_model=Token)
async def login_for_access_token(form_date: OAuth2PasswordRequestForm = Depends()):
    user = jwt_authenticate_user(db=fake_users_db, username=form_date.username, password=form_date.password)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password"
        )
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.username},
        expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}

async def jwt_get_current_user(token: str = Depends(oauth2_schema)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"}  # OAuth2请求规范，如果认证失败，请求头中返回WWW-Authenticate
    )

    try:
        payload = jwt.decode(token=token, key=SECRET_KEY, algorithm=ALGORITHM)
        username = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception

    user = jwt_get_user(db=fake_users_db, username=username)
    if user is None:
        raise credentials_exception
    return user

async def jwt_get_current_active_user(current_user: User = Depends(jwt_get_user)):
    if not current_user.disabled:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Inactive user"
        )
    return current_user


@chapter04.get('/jwt/users/me')
async def jwt_read_users_me(current_user: User = Depends(jwt_get_current_active_user)):
    return current_user
